﻿using System.Drawing;
using System.Globalization;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using System.Net;
using System.Reflection;
using Devonline.Communication.Messages;
using Devonline.Utils;
using Mapster;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.DynamicLinq;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;

namespace Devonline.AspNetCore;

/// <summary>
/// 公共数据处理服务
/// </summary>
/// <typeparam name="TDbContext">数据库上下文</typeparam>
/// <typeparam name="TEntitySet">业务数据类型</typeparam>
/// <typeparam name="TKey">键类型</typeparam>
public class DataServiceX<TDbContext, TEntitySet, TKey> :
    IDataService<TDbContext, TEntitySet, TKey>
    where TDbContext : DbContext
    where TEntitySet : class, IEntitySet<TKey>
    where TKey : IConvertible
{
    protected readonly TDbContext _context;
    protected readonly ILogger<DataService<TDbContext, TEntitySet, TKey>> _logger;
    protected readonly IDistributedCache _cache;
    protected readonly HttpContext _httpContext;
    protected readonly HttpRequest _request;
    protected readonly AppSetting _appSetting;
    protected readonly IMessageCommunicator _communicator;
    protected readonly bool _enableCache;
    protected readonly bool _localCache;
    protected readonly TypeAdapterConfig _adapterConfig;
    protected readonly DbSet<TEntitySet> _dbSet;
    protected readonly Type _type;
    protected readonly string _typeName;

    /// <summary>
    /// 构造方法
    /// </summary>
    /// <param name="logger">日志</param>
    /// <param name="context">数据库上下文</param>
    /// <param name="httpContextAccessor">Http上下文</param>
    /// <param name="cache">缓存</param>
    /// <param name="appSetting">配置</param>
    public DataServiceX(
        TDbContext context,
        ILogger<DataService<TDbContext, TEntitySet, TKey>> logger,
        IDistributedCache cache,
        IHttpContextAccessor httpContextAccessor,
        IMessageCommunicator communicator,
        AppSetting appSetting
        )
    {
        if (httpContextAccessor.HttpContext == null)
        {
            throw new ArgumentNullException(nameof(httpContextAccessor.HttpContext), "HttpContext can't be null!");
        }

        _context = context;
        _logger = logger;
        _cache = cache;
        _appSetting = appSetting;
        _httpContext = httpContextAccessor.HttpContext;
        _request = _httpContext.Request;
        _communicator = communicator;
        _dbSet = _context.Set<TEntitySet>();
        _type = typeof(TEntitySet);
        _typeName = typeof(TEntitySet).GetDisplayName();
        _adapterConfig = GetAdapterConfig();
        _enableCache = _appSetting.EnableCache && _type.HasAttribute<CacheableAttribute>();
        _localCache = _enableCache && _type.GetAttributeValue<CacheableAttribute, bool>(nameof(CacheableAttribute.LocalCache));
        UserName = _httpContext.User.Identity?.Name ?? AppSettings.USER_ANONYMOUS;
    }

    #region 基础部分
    #region 基础方法
    /// <summary>
    /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, 
    /// UpdateAsync and DeleteAsync are called.
    /// True if changes should be automatically persisted, otherwise false.
    /// </summary>
    public virtual bool AutoSaveChanges { get; set; } = true;
    /// <summary>
    /// 当前登录的用户
    /// </summary>
    public virtual string UserName { get; private set; }
    /// <summary>
    /// 从 httpContext 获取用户标识, 用户标识在 User.Claims 中的 type 为 sub; 在 request 中为 userId
    /// </summary>
    /// <returns></returns>
    public virtual string? GetUserId() => _httpContext.GetUserId();
    /// <summary>
    /// 从 httpContext 获取用户标识, 用户标识在 User.Claims 中的 type 为 userName
    /// </summary>
    /// <returns></returns>
    public virtual string? GetUserName() => _httpContext.GetUserName();
    /// <summary>
    /// 获取查询选项列表
    /// </summary>
    /// <returns>返回合并后的查询选项</returns>
    public virtual IEnumerable<KeyValuePair<string, StringValues>> GetRequestOptions()
    {
        var queryString = _request.Query.ToList();
        if (_request.HasFormContentType && (_request.Form.Keys?.Count ?? 0) > 0)
        {
            queryString.AddRange(_request.Form);
        }

        if (_request.Headers.IsNotNullOrEmpty())
        {
            queryString.AddRange(_request.Headers);
        }

        if (_request.Cookies.IsNotNullOrEmpty())
        {
            queryString.AddRange(_request.Cookies.ToDictionary(x => x.Key, x => new StringValues(x.Value)));
        }

        return queryString;
    }
    /// <summary>
    /// 从当前 request 中获取参数的值, 取值顺序 Query -> Form -> Header -> Cookie
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public virtual T? GetRequestOption<T>(string key) where T : IConvertible
    {
        var value = string.Empty;
        if (_request.Query.Keys.Contains(key))
        {
            value = _request.Query[key];
        }
        else if (_request.HasFormContentType && _request.Form.Keys.IsNotNullOrEmpty() && _request.Form.Keys.Contains(key))
        {
            value = _request.Form[key];
        }
        else if (_request.Headers.IsNotNullOrEmpty() && _request.Headers.ContainsKey(key))
        {
            value = _request.Headers[key];
        }
        else if (_request.Cookies.IsNotNullOrEmpty() && _request.Cookies.Keys.Contains(key))
        {
            value = _request.Cookies[key];
        }

        return string.IsNullOrWhiteSpace(value) ? default : value.To<T>();
    }
    /// <summary>
    /// 获取上下文对象中参数/变量的值, 取值顺序 HttpContext -> Query -> Form -> Header -> Cookie
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public virtual T? GetContextOption<T>(string key) where T : IConvertible
    {
        var value = _httpContext.User.Claims.FirstOrDefault(c => c.Type == key)?.Value;
        return string.IsNullOrWhiteSpace(value) ? _httpContext.Request.GetRequestOption<T>(key) : value.To<T>();
    }
    /// <summary>
    /// 调用实例的 Create 方法为基础字段赋值
    /// </summary>
    /// <typeparam name="TModel">业务数据类型</typeparam>
    /// <param name="model">业务数据</param>
    public virtual void Create<TModel>(TModel model) where TModel : class, IEntitySet<TKey>
    {
        if (model is IEntitySetWithCreate<TKey> entitySetWithCreate)
        {
            entitySetWithCreate.Create(GetUserName());
        }
    }
    /// <summary>
    /// 调用实例的 Update 方法为基础字段赋值
    /// </summary>
    /// <typeparam name="TModel">业务数据类型</typeparam>
    /// <param name="model">业务数据</param>
    public virtual void Update<TModel>(TModel model) where TModel : class, IEntitySet<TKey>
    {
        if (model is IEntitySetWithCreateAndUpdate<TKey> entitySetWithCreateAndUpdate)
        {
            entitySetWithCreateAndUpdate.Update(GetUserName());
        }
    }
    /// <summary>
    /// 从当前上下文解除当前对象的跟踪状态
    /// </summary>
    /// <typeparam name="TModel">业务数据类型</typeparam>
    /// <param name="model">业务数据</param>
    public virtual void Detach<TModel>(TModel model) where TModel : class, IEntitySet<TKey>
    {
        var exist = _dbSet.Local.FirstOrDefault(x => x.Id.Equals(model.Id));
        if (exist != null)
        {
            _context.Entry(exist).State = EntityState.Detached;
        }
    }
    /// <summary>
    /// 单个数据对象的新增, 自动验证重复, 此方法不提交
    /// 如果传入的业务数据类型是一个 IViewModel<TKey>, 则将业务数据当成当前实体类型的 ViewModel, 进行字段隐射, 并添加到数据库上下文, 此时只能返回处理后的 ViewModel
    /// 否则可接受任意来自 IEntitySet<TKey> 的实体对象类型
    /// </summary>
    /// <typeparam name="TModel">业务数据类型</typeparam>
    /// <param name="model">业务数据</param>
    /// <returns></returns>
    public virtual async Task AddAsync<TModel>(TModel model) where TModel : class, IEntitySet<TKey>
    {
        await ThrowIfExistAsync(model.Id);
        await UniqueAsync(model);
        var entitySet = model.Adapt<TModel, TEntitySet>(_adapterConfig);
        Create(entitySet);
        Update(entitySet);
        Detach(entitySet);
        await _dbSet.AddAsync(entitySet);
    }
    /// <summary>
    /// 单个数据对象的更新, 自动验证重复, 此方法不提交
    /// 更新阶段判断满足条件的数据是否存在, 存在则抛出异常, 不存在则执行基类预定义赋值操作, 自动验证重复
    /// </summary>
    /// <typeparam name="TModel">业务数据类型</typeparam>
    /// <param name="model">业务数据</param>
    /// <param name="isLogical">是否逻辑操作, 默认不是</param>
    /// <returns></returns>
    public virtual async Task UpdateAsync<TModel>(TModel model, bool isLogical = false) where TModel : class, IEntitySet<TKey>
    {
        var entitySet = await GetIfExistAsync(model.Id);
        await UniqueAsync(model);

        if (isLogical)
        {
            LogicUpdate(entitySet);
        }

        model.Adapt(entitySet, _adapterConfig);
        Update(entitySet);
        _dbSet.Update(entitySet);
    }
    /// <summary>
    /// 删除/逻辑删除当前业务对象, 此方法不提交
    /// </summary>
    /// <param name="id">业务数据主键</param>
    /// <param name="isLogical">是否逻辑操作, 默认不是</param>
    public virtual async Task DeleteAsync(TKey id, bool isLogical = false)
    {
        var entitySet = await GetIfExistAsync(id);
        if (isLogical && entitySet is IEntitySetWithCreate<TKey> entitySetWithCreate)
        {
            Update(entitySet);
            entitySetWithCreate.State = DataState.Deleted;
            Detach(entitySet);
            //_dbSet.Update((TEntitySet)entitySetWithCreate);
            _dbSet.Update(entitySet);
        }
        else
        {
            _dbSet.Remove(entitySet);
        }
    }
    /// <summary>
    /// 重载基类保存方法, 变更成功后自动触发缓存刷新
    /// </summary>
    /// <returns></returns>
    public virtual async Task<int> SaveChangesAsync()
    {
        var result = await _context.SaveChangesAsync();
        if (_localCache)
        {
            _logger.LogInformation("Type {typeName} has been changed, and will notice the client side to refresh cache", _typeName);
            await _communicator.RefreshCacheAsync(_type.Name);
        }

        return result;
    }
    #endregion

    #region 数据查询方法
    /// <summary>
    /// 非注入类型 TEntitySet 的统一数据源查询入口, 可缓存数据源, 可返回泛型类型指定的任意类型引用
    /// 缓存的前提是全局缓存开关开启且当前数据对象类型标记为可缓存
    /// </summary>
    /// <typeparam name="TEntitySet">查询的类型</typeparam>
    /// <returns></returns>
    public virtual IQueryable<TEntitySet> GetQueryable()
    {
        if (_enableCache)
        {
            var cacheKey = AppSettings.CACHE_DATA + _type.Name.ToUpper(CultureInfo.CurrentCulture);
            var entitySets = _cache.GetValue<IEnumerable<TEntitySet>>(cacheKey);
            if (entitySets == null)
            {
                _logger.LogInformation("{type} have no cache data, will query and cached", _typeName);
                entitySets = _dbSet.ToHashSet();
                _cache.SetValue(cacheKey, entitySets);
            }

            return entitySets.AsQueryable();
        }

        _logger.LogDebug("The user {user} query {typeName}", UserName, _typeName);
        return _dbSet;
    }
    /// <summary>
    /// 非注入类型 TEntitySet 的统一数据源查询入口, 可缓存数据源, 可返回泛型类型指定的任意类型引用
    /// 缓存的前提是全局缓存开关开启且当前数据对象类型标记为可缓存
    /// </summary>
    /// <returns></returns>
    public virtual IQueryable<TModel> GetQueryable<TModel>() where TModel : class
    {
        var typeName = typeof(TModel).GetDisplayName();
        var queryable = _context.Set<TModel>();
        if (_enableCache)
        {
            var cacheKey = AppSettings.CACHE_DATA + _type.Name.ToUpper(CultureInfo.CurrentCulture);
            var entitySets = _cache.GetValue<IEnumerable<TModel>>(cacheKey);
            if (entitySets == null)
            {
                _logger.LogInformation("{type} have no cache data, will query and cached", typeName);
                entitySets = queryable.ToHashSet();
                _cache.SetValue(cacheKey, entitySets);
            }

            return queryable.AsQueryable();
        }

        _logger.LogDebug("The user {user} query {typeName}", UserName, typeName);
        return queryable;
    }
    /// <summary>
    /// 非注入类型 TEntitySet 的统一数据源查询入口, 可缓存数据源, 可返回泛型类型指定的任意类型引用
    /// 缓存的前提是全局缓存开关开启且当前数据对象类型标记为可缓存
    /// </summary>
    /// <typeparam name="TEntitySet">查询的类型</typeparam>
    /// <param name="predicate">过滤条件</param>
    /// <returns></returns>
    public virtual IQueryable<TEntitySet> GetQueryable(Expression<Func<TEntitySet, bool>> predicate) => GetQueryable().Where(predicate);
    /// <summary>
    /// 根据 id 获取对象
    /// </summary>
    /// <typeparam name="TEntitySet">查询的类型</typeparam>
    /// <param name="id"></param>
    /// <returns></returns>
    public virtual async Task<TEntitySet?> GetAsync(TKey id) => await GetQueryable().FirstOrDefaultAsync(x => x.Id.Equals(id));
    /// <summary>
    /// 从 QueryOptions 和上下文自动获取查询表达式的内容并执行查询返回分页的结果
    /// 暂不支持 orderby, select 和 expand 表达式
    /// </summary>
    /// <returns></returns>
    public virtual async Task<PagedResult> GetPagedResultAsync()
    {
        var queryable = GetQueryable();
        var queryOptions = new QueryOptions<TEntitySet>(new QueryOptionRawValue(_httpContext.Request.Query));
        if (queryOptions.Filter != null)
        {
            if (queryOptions.Filter is NestedFilterOption<TEntitySet> filter && filter.Filters != null && filter.Filters.Any())
            {
                //目前仅支持一级嵌套, 且顶级条件仅支持 and
                //var expression = string.Join(GetFilterLogic(filter.Logic ?? LogicType.And), filter.Filters.Select(x => GetFilterExpression(x)));
                queryable = queryable.Where(string.Join(filter.GetFilterLogic(), filter.Filters.Select(x => x.GetFilterExpression())));
            }
            else if ((!string.IsNullOrWhiteSpace(queryOptions.Filter.Field)) && (!string.IsNullOrWhiteSpace(queryOptions.Filter.Value)))
            {
                queryable = queryable.Where(queryOptions.Filter.GetFilterExpression(), queryOptions.Filter.FieldValue);
            }
        }

        if (queryOptions.Orderby != null)
        {
            queryable = queryable.OrderBy(queryOptions.Orderby.ToString());
        }

        var result = new PagedResult();
        if (queryOptions.Count.HasValue && queryOptions.Count.Value)
        {
            result.Total = await queryable.CountAsync();
        }

        if (queryOptions.Skip.HasValue && queryOptions.Skip.Value > 0)
        {
            queryable = queryable.Skip(queryOptions.Skip.Value);
        }

        if (queryOptions.Top.HasValue && queryOptions.Top.Value > 0)
        {
            queryable = queryable.Take(queryOptions.Top.Value);
            result.PageSize = queryOptions.Top.Value;
        }

        result.PageIndex = (queryOptions.Skip ?? 1) / result.PageSize + 1;

        if (queryOptions.Select != null)
        {
            var columns = queryOptions.Select.ToString<string>();
            result.Rows = await queryable.Select("new {" + columns + "}", null).ToDynamicListAsync();
        }
        else
        {
            result.Rows = await queryable.ToListAsync();
        }

        return result;
    }
    /// <summary>
    /// 获取业务数据的集合成员, 成员是附加信息, 附件及特定类型的附属集合
    /// </summary>
    /// <typeparam name="TEntitySet">业务数据类型</typeparam>
    /// <typeparam name="TMember">附加信息, 附件或附属集合的类型</typeparam>
    /// <param name="entitySet">业务数据</param>
    /// <returns></returns>
    public virtual IEnumerable<TMember>? GetCollectionMembers<TMember>(TEntitySet entitySet) where TMember : class, IEntitySet<TKey>
    {
        var propertyInfo = _type.GetProperties(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(x => x.PropertyType.IsFromType<IEnumerable<TMember>>());
        if (propertyInfo != null && propertyInfo.GetValue(entitySet) is IEnumerable<TMember> members)
        {
            return members;
        }

        return null;
    }
    /// <summary>
    /// 适用于新增时判断对象是否存在, 存在则抛出异常
    /// </summary>
    /// <typeparam name="TEntitySet">查询的类型</typeparam>
    /// <param name="id">业务数据编号</param>
    /// <returns></returns>
    /// <exception cref="BadHttpRequestException"></exception>
    public virtual async Task ThrowIfExistAsync(TKey? id)
    {
        if ((!(id == null || id.Equals(default))) && await GetQueryable().AnyAsync(x => x.Id.Equals(id)))
        {
            throw new BadHttpRequestException($"{_typeName} 中当前记录已存在!");
        }
    }
    /// <summary>
    /// 适用于更新时判断当前对象是否存在, 存在则返回按 Id 查询的结果, 不存在则抛出异常
    /// </summary>
    /// <typeparam name="TEntitySet">查询的类型</typeparam>
    /// <param name="id">业务数据主键</param>
    /// <returns></returns>
    /// <exception cref="BadHttpRequestException"></exception>
    public virtual async Task<TEntitySet> GetIfExistAsync(TKey id)
    {
        var entitySet = await GetAsync(id);
        if (entitySet == null)
        {
            throw new BadHttpRequestException($"{_typeName} 中当前记录不存在!");
        }

        return entitySet;
    }
    #endregion

    #region 数据操作方法
    /// <summary>
    /// 获取在更新复制属性值时需要忽略的字段配置
    /// </summary>
    /// <param name="adapterConfig">TypeAdapterConfig 隐射配置委托</param>
    /// <returns></returns>
    public virtual void AdapterConfig(Action<TypeAdapterSetter<TEntitySet>> adapterConfig) => adapterConfig(_adapterConfig.ForDestinationType<TEntitySet>());
    /// <summary>
    /// 新增或修改阶段判断满足条件的数据是否存在, 存在则抛出异常, 不存在则执行基类预定义赋值操作
    /// </summary>
    /// <typeparam name="TModel">业务数据的 ViewModel 数据类型</typeparam>
    /// <param name="model">业务数据的 ViewModel</param>
    /// <returns></returns>
    /// <exception cref="BadHttpRequestException"></exception>
    public virtual async Task UniqueAsync<TModel>(TModel model) where TModel : class, IEntitySet<TKey>
    {
        var propertyInfos = typeof(TModel).GetProperties().Where(x => x.HasAttribute<UniqueAttribute>());
        if (propertyInfos.IsNotNullOrEmpty())
        {
            var index = 0;
            var keys = new List<string>();
            var values = new List<object>();
            var messages = new List<string>();

            if (!(model.Id == null || model.Id.Equals(default)))
            {
                keys.Add($"{nameof(model.Id)} != @{index++}");
                values.Add($"{model.Id}");
            }

            foreach (var propertyInfo in propertyInfos)
            {
                var value = propertyInfo.GetValue(model);
                keys.Add($"{propertyInfo.Name} == @{index++}");
                values.Add($"{value}");
                messages.Add($"<{propertyInfo.GetDisplayName()}> = {value}");
            }

            var predicate = string.Join(" and ", keys);
            var exist = await GetQueryable().AnyAsync(predicate, values.ToArray());
            if (exist)
            {
                throw new BadHttpRequestException($"{_typeName} 中 {messages.ToString<string>()} 的记录已经存在!");
            }
        }
    }
    /// <summary>
    /// 单个数据对象的新增, 自动验证重复
    /// </summary>
    /// <param name="entitySet">业务数据</param>
    /// <returns></returns>
    public virtual async Task<TEntitySet> AddAsync(TEntitySet entitySet)
    {
        _logger.LogDebug("The user {user} will add {typeName}, the content is: {content}", UserName, _typeName, model.ToJsonString());
        await AddAsync<TEntitySet>(entitySet);

        if (AutoSaveChanges)
        {
            await SaveChangesAsync();
            _logger.LogInformation("The user {user} add {typeName} success, the content is: {content}", UserName, _typeName, entitySet.ToJsonString());
        }

        return entitySet;
    }
    /// <summary>
    /// 批量新增, 自动验证重复
    /// </summary>
    /// <param name="entitySets">业务数据</param>
    /// <returns></returns>
    public virtual async Task<IEnumerable<TEntitySet>> AddsAsync(IEnumerable<TEntitySet> entitySets)
    {
        if (!entitySets.Any())
        {
            return entitySets;
        }

        _logger.LogDebug("The user {user} will adds {typeName}, the content is: {content}", UserName, _typeName, entitySets.ToJsonString());

        foreach (var entitySet in entitySets)
        {
            await AddAsync<TEntitySet>(entitySet);
        }

        if (AutoSaveChanges)
        {
            await SaveChangesAsync();
            _logger.LogInformation("The user {user} adds {typeName} success, the content is: {content}", UserName, _typeName, entitySets.ToJsonString());
        }

        return entitySets;
    }
    /// <summary>
    /// 更新阶段判断满足条件的数据是否存在, 存在则抛出异常, 不存在则执行基类预定义赋值操作, 自动验证重复
    /// </summary>
    /// <param name="entitySet">业务数据</param>
    /// <param name="isLogical">是否逻辑操作, 默认不是</param>
    /// <returns></returns>
    public virtual async Task<TEntitySet> UpdateAsync(TEntitySet entitySet, bool isLogical = false)
    {
        _logger.LogDebug("The user {user} will {isLogical} update {typeName}, the content is: {content}", UserName, IsLogicalString(isLogical), _typeName, entitySet.ToJsonString());
        await UpdateAsync<TEntitySet>(entitySet, isLogical);

        if (AutoSaveChanges)
        {
            await SaveChangesAsync();
            _logger.LogInformation("The user {user} add {typeName} success, the content is: {content}", UserName, _typeName, entitySet.ToJsonString());
        }

        return entitySet;
    }
    /// <summary>
    /// 批量新增或修改数据对象模型到当前上下文, 但不提交
    /// </summary>
    /// <param name="entitySets">业务数据</param>
    /// <param name="isLogical">是否逻辑操作, 默认不是</param>
    /// <returns></returns>
    public virtual async Task<IEnumerable<TEntitySet>> AddsOrUpdatesAsync(IEnumerable<TEntitySet> entitySets, bool isLogical = false)
    {
        if (!entitySets.Any())
        {
            return entitySets;
        }

        _logger.LogDebug("The user {user} will {isLogical} adds or updates {typeName}, the content is: {content}", UserName, IsLogicalString(isLogical), _typeName, entitySets.ToJsonString());
        var list = new List<TEntitySet>();
        var ids = entitySets.Select(x => x.Id).Distinct();
        var entities = await GetQueryable().Where(x => ids.Contains(x.Id)).ToListAsync();
        foreach (var entitySet in entitySets)
        {
            await UniqueAsync(entitySet);
            var entity = entities.FirstOrDefault(x => x.Id.Equals(entitySet.Id));
            if (entity == null)
            {
                Create(entitySet);
                Update(entitySet);
                Detach(entitySet);
                list.Add(entitySet);
                await _dbSet.AddAsync(entitySet);
            }
            else
            {
                if (isLogical)
                {
                    LogicUpdate(entity);
                }

                entitySet.Adapt(entity, _adapterConfig);
                Update(entity);
                list.Add(entity);
                _dbSet.Update(entity);
            }
        }

        if (AutoSaveChanges)
        {
            await SaveChangesAsync();
            _logger.LogInformation("The user {user} {isLogical} adds or updates {typeName} success, the content is: {content}", UserName, IsLogicalString(isLogical), _typeName, entitySets.ToJsonString());
        }

        return list;
    }
    /// <summary>
    /// 删除记录
    /// </summary>
    /// <param name="id">业务数据主键</param>
    /// <param name="isLogical">是否逻辑操作, 默认不是</param>
    /// <returns></returns>
    public virtual async Task DeleteAsync(TKey id, bool isLogical = false)
    {
        _logger.LogDebug("The user {user} will delete {isLogical} {typeName}, the id is: {id}", UserName, IsLogicalString(isLogical), _typeName, id);
        await DeleteAsync(id, isLogical);

        if (AutoSaveChanges)
        {
            await SaveChangesAsync();
            _logger.LogInformation("The user {user} {isLogical} delete {typeName} success, the id is: {id}", UserName, IsLogicalString(isLogical), _typeName, id);
        }
    }
    /// <summary>
    /// 批量删除记录
    /// </summary>
    /// <param name="ids">业务数据主键</param>
    /// <param name="isLogical">是否逻辑操作, 默认不是</param>
    /// <returns></returns>
    public virtual async Task DeletesAsync(IEnumerable<TKey> ids, bool isLogical = false)
    {
        var stringIds = ids.ToString(AppSettings.DEFAULT_SPLITER_STRING);
        _logger.LogDebug("The user {user} will {isLogical} deletes {typeName}, the ids are: {id}", UserName, IsLogicalString(isLogical), _typeName, stringIds);
        var entitySets = await GetQueryable(x => ids.Contains(x.Id)).ToListAsync();
        if (entitySets == null || entitySets.Any(x => !ids.Contains(x.Id)))
        {
            throw new BadHttpRequestException($"{typeof(TEntitySet).GetDisplayName()} 中当前记录不存在!");
        }

        foreach (var entitySet in entitySets)
        {
            await DeleteAsync(entitySet, isLogical);
        }

        if (AutoSaveChanges)
        {
            await SaveChangesAsync();
            _logger.LogInformation("The user {user} {isLogical} deletes {typeName} success, the ids are: {id}", UserName, IsLogicalString(isLogical), _typeName, stringIds);
        }
    }
    #endregion
    #endregion

    #region 扩展部分
    #region 附件类基础属性
    /// <summary>
    /// 允许上传的文件类型
    /// </summary>
    protected string[] AllowFileExtensions => (_appSetting.Attachment?.AllowFileExtensions ?? AppSettings.DEFAULT_ALLOW_FILE_EXTENSIONS).Split(AppSettings.CHAR_COMMA, StringSplitOptions.RemoveEmptyEntries);
    /// <summary>
    /// 图片文件缩略图前缀
    /// </summary>
    protected string ImageThumbnailPrefix => _appSetting.Attachment?.ImageThumbnailPrefix ?? AppSettings.DEFAULT_IMAGE_THUMBNAIL_PREFIX;
    /// <summary>
    /// 图片文件缩略图尺寸
    /// </summary>
    protected int ImageThumbnailSize => _appSetting.Attachment?.ImageThumbnailSize ?? AppSettings.DEFAULT_IMAGE_THUMBNAIL_SIZE;
    /// <summary>
    /// 单个文件最大允许上传的大小
    /// </summary>
    protected int MaxUploadFileSize
    {
        get
        {
            var fileSize = _appSetting.Attachment?.MaxUploadFileSize ?? AppSettings.DEFAULT_MAX_UPLOAD_FILE_SIZE;
            if (fileSize <= 0)
            {
                fileSize = AppSettings.DEFAULT_MAX_UPLOAD_FILE_SIZE;
            }

            return fileSize;
        }
    }
    /// <summary>
    /// 单次上传允许的总计文件大小
    /// </summary>
    protected int TotalUploadFileSize
    {
        get
        {
            var fileSize = _appSetting.Attachment?.TotalUploadFileSize ?? AppSettings.DEFAULT_TOTAL_UPLOAD_FILE_SIZE;
            if (fileSize <= 0)
            {
                fileSize = AppSettings.DEFAULT_TOTAL_UPLOAD_FILE_SIZE;
            }

            return fileSize;
        }
    }
    #endregion

    #region 业务附加信息类操作方法
    /// <summary>
    /// 新增业务数据的附加信息
    /// </summary>
    /// <typeparam name="TAdditional">附加信息数据类型</typeparam>
    /// <param name="additionals">附加数据集合</param>
    /// <param name="additionalId">业务数据主键</param>
    /// <returns></returns>
    public virtual async Task AddAdditionalsAsync<TAdditional>(IEnumerable<TAdditional>? additionals, TKey additionalId) where TAdditional : class, IAdditional<TKey>
    {
        if (additionals != null && additionals.Any())
        {
            var _dbSetAdditional = _context.Set<TAdditional>();
            foreach (var entity in additionals)
            {
                entity.AdditionalId = additionalId;
                Create(entity);
                Detach(entity);
                await _dbSetAdditional.AddAsync(entity);
            }
        }
    }
    /// <summary>
    /// 更新业务数据的附加信息
    /// </summary>
    /// <typeparam name="TAdditional">附加信息数据类型</typeparam>
    /// <param name="additionals">附加数据集合</param>
    /// <param name="additionalId">业务数据主键</param>
    /// <param name="isLogical">是否逻辑操作, 默认不是</param>
    /// <returns></returns>
    public virtual async Task UpdateAdditionalsAsync<TAdditional>(IEnumerable<TAdditional>? additionals, TKey additionalId, bool isLogical = false) where TAdditional : class, IAdditional<TKey>
    {
        var _dbSetAdditional = _context.Set<TAdditional>();
        var news = additionals ?? new List<TAdditional>();
        var olds = await GetQueryable<TAdditional>().Where(x => x.AdditionalId != null && x.AdditionalId.Equals(additionalId)).ToListAsync() ?? new List<TAdditional>();
        foreach (var entity in news)
        {
            var old = olds.FirstOrDefault(x => x.Id != null && x.Id.Equals(entity.Id));
            if (old == null)
            {
                //新的有旧的没有, 则新增
                entity.AdditionalId = additionalId;
                Create(entity);
                Detach(entity);
                _dbSetAdditional.Add(entity);
            }
            else
            {
                //新的有旧的也有, 则更新
                if (isLogical)
                {
                    LogicUpdate(old);
                }

                //old.State = entity.State;
                old.Index = entity.Index;
                old.Key = entity.Key;
                old.Value = entity.Value;
                Update(old);
                _dbSetAdditional.Update(old);
            }
        }

        foreach (var old in olds)
        {
            if (!news.Any(x => x.Id != null && x.Id.Equals(old.Id)))
            {
                Delete(old, isLogical);
            }
        }
    }
    /// <summary>
    /// 删除业务数据的附加信息
    /// </summary>
    /// <typeparam name="TAdditional">附加信息数据类型</typeparam>
    /// <param name="additionalId">业务数据主键</param>
    /// <param name="isLogical">是否逻辑操作, 默认不是</param>
    /// <returns></returns>
    public virtual async Task DeleteAdditionalsAsync<TAdditional>(TKey additionalId, bool isLogical = false) where TAdditional : class, IAdditional<TKey>
    {
        var additionals = await GetQueryable<TAdditional>().Where(x => x.AdditionalId != null && x.AdditionalId.Equals(additionalId)).ToListAsync();
        if (additionals != null && additionals.Count > 0)
        {
            foreach (var additional in additionals)
            {
                Delete(additional, isLogical);
            }
        }
    }
    #endregion

    #region 业务有引用关系的集合类操作方法
    /// <summary>
    /// 新增业务数据对象中 TElement 类型的集合对象
    /// </summary>
    /// <typeparam name="TElement">集合元素数据类型</typeparam>
    /// <param name="collection">引用数据集合</param>
    /// <param name="id">当前业务对象主键</param>
    /// <param name="foreignKey">外键字段名</param>
    /// <returns></returns>
    public virtual async Task AddCollectionAsync<TElement>(IEnumerable<TElement>? collection, TKey id, string? foreignKey = null) where TElement : class, IEntitySet<TKey>
    {
        if (collection != null && collection.Any())
        {
            var _dbSetElement = _context.Set<TElement>();
            var propertyInfo = typeof(TElement).GetProperty(GetForeignKey<TElement>(foreignKey));
            if (propertyInfo != null)
            {
                foreach (var entity in collection)
                {
                    Create(entity);
                    Update(entity);
                    Detach(entity);
                    propertyInfo.SetValue(entity, id);
                    await _dbSetElement.AddAsync(entity);
                }
            }
        }
    }
    /// <summary>
    /// 更新业务数据对象中 TElement 类型的集合对象
    /// </summary>
    /// <typeparam name="TElement">集合元素数据类型</typeparam>
    /// <param name="collection">引用数据集合</param>
    /// <param name="id">当前业务对象主键</param>
    /// <param name="foreignKey">外键字段名</param>
    /// <returns></returns>
    public virtual async Task UpdateCollectionAsync<TElement>(IEnumerable<TElement>? collection, TKey id, string? foreignKey = null, bool isLogical = false) where TElement : class, IEntitySet<TKey>
    {
        if (collection != null && collection.Any())
        {
            var _dbSetElement = _context.Set<TElement>();
            foreignKey = GetForeignKey<TElement>(foreignKey);
            var propertyInfo = typeof(TElement).GetProperty(foreignKey);
            if (propertyInfo != null)
            {
                var olds = await GetQueryable<TElement>().Where($"{foreignKey} == @0", id).ToListAsync();
                foreach (var entity in collection)
                {
                    var old = olds.FirstOrDefault(x => x.Id.Equals(entity.Id));
                    if (old == null)
                    {
                        //新的有旧的没有, 则新增
                        Create(entity);
                        Update(entity);
                        Detach(entity);
                        propertyInfo.SetValue(entity, id);
                        await _dbSetElement.AddAsync(entity);
                    }
                    else
                    {
                        //新的有旧的也有, 则更新
                        if (isLogical)
                        {
                            LogicUpdate(old);
                        }

                        entity.Adapt(old, _adapterConfig);
                        Update(old);
                        _dbSetElement.Update(old);
                    }
                }

                foreach (var old in olds)
                {
                    if (!collection.Any(x => x.Id.Equals(old.Id)))
                    {
                        Delete(old, isLogical);
                    }
                }
            }
        }
    }
    /// <summary>
    /// 删除业务数据对象中 TElement 类型的集合对象
    /// </summary>
    /// <typeparam name="TElement">集合元素数据类型</typeparam>
    /// <param name="id">业务数据主键</param>
    /// <param name="foreignKey">外键字段名</param>
    /// <returns></returns>
    public virtual async Task DeleteCollectionAsync<TElement>(TKey id, string? foreignKey = null, bool isLogical = false) where TElement : class, IEntitySet<TKey>
    {
        var elements = await GetQueryable<TElement>().Where($"{GetForeignKey<TElement>(foreignKey)} == @0", id).ToListAsync();
        if (elements != null && elements.Count > 0)
        {
            foreach (var element in elements)
            {
                Delete(element, isLogical);
            }
        }
    }
    #endregion

    #region 业务附件类操作方法
    /// <summary>
    /// 获取附件绝对地址
    /// </summary>
    /// <param name="fileName">附件文件名</param>
    /// <returns></returns>
    public virtual string GetAttachmentPath(string fileName) => Path.Combine((_appSetting.Attachment?.RootPath ?? AppSettings.DEFAULT_ATTACHMENT_PATH).GetAbsolutePath(), fileName);
    /// <summary>
    /// 获取 fileName 同扩展名临时文件名
    /// </summary>
    /// <param name="fileName"></param>
    /// <returns></returns>
    public virtual string GetTempFileName(string fileName, string? extension = null)
    {
        var filePath = DateTime.UtcNow.ToString(AppSettings.DEFAULT_DATE_FORMAT, CultureInfo.InvariantCulture);
        var attachmentPath = GetAttachmentPath(filePath);
        if (!Directory.Exists(attachmentPath))
        {
            Directory.CreateDirectory(attachmentPath);
        }

        return Path.Combine(filePath, KeyGenerator.GetKey() + (extension ?? Path.GetExtension(fileName)));
    }
    /// <summary>
    /// 获取并返回文件名指向的物理文件
    /// </summary>
    /// <param name="fileName">文件名</param>
    /// <returns></returns>
    public virtual IActionResult GetFile(string fileName)
    {
        var filePath = GetAttachmentPath(fileName);
        if (!File.Exists(filePath))
        {
            _logger.LogWarning("The file {fileName} not found!", fileName);
            return new NotFoundResult();
        }

        _logger.LogInformation("The user {user} get the file from path <{filePath}> and name <{fileName}> success", UserName, filePath, fileName);
        return new PhysicalFileResult(filePath, ContentType.Default) { FileDownloadName = Path.GetFileName(fileName) };
    }
    /// <summary>
    /// 删除附件物理文件, 请在附件相关操作的事物提交之后执行此方法
    /// </summary>
    /// <param name="files">待删除的文件</param>
    public virtual void DeleteFiles(IEnumerable<string> files)
    {
        if (files.Any())
        {
            foreach (var file in files)
            {
                try
                {
                    _logger.LogInformation("user will delete the file {file}", file);
                    var filePath = GetAttachmentPath(file);
                    if (File.Exists(filePath))
                    {
                        _logger.LogInformation("the file {file} exist, then will delete", file);
                        File.Delete(filePath);
                    }

                    //如果存在文件缩略图则删除
                    var dirPath = Path.GetDirectoryName(filePath);
                    if (Directory.Exists(dirPath))
                    {
                        var newFileName = Path.Combine(dirPath, ImageThumbnailPrefix + Path.GetFileName(filePath));
                        if (File.Exists(newFileName))
                        {
                            _logger.LogInformation("the file {file} thumbnail exist, then will delete file thumbnail", file);
                            File.Delete(newFileName);
                        }
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "file {file} delete throw exception", file);
                }
            }

            _logger.LogInformation("User {user} delete the files <{fileNames}> from file system success", UserName, files.ToString<string>());
        }
    }

    /// <summary>
    /// 获取附件对应的文件, 适用于文件下载
    /// </summary>
    /// <typeparam name="TAttachment">附件</typeparam>
    /// <param name="fileName"></param>
    /// <param name="filePath"></param>
    /// <returns></returns>
    /// <exception cref="BadHttpRequestException"></exception>
    public virtual async Task<IActionResult> GetAttachmentFileAsync<TAttachment>(string? fileName, string? filePath = null) where TAttachment : class, IAttachment<TKey>, new()
    {
        var attachment = await GetAttachmentAsync<TAttachment>(fileName, filePath);
        if (attachment == null || string.IsNullOrWhiteSpace(attachment.Path))
        {
            _logger.LogError("File {filePath}/{fileName} not found!", filePath, fileName);
            return new NotFoundResult();
        }

        filePath = GetAttachmentPath(attachment.Path);
        if (!File.Exists(filePath))
        {
            _logger.LogError("File {filePath}/{fileName} not found!", filePath, fileName);
            return new NotFoundResult();
        }

        _logger.LogInformation("User {user} get the file from path <{filePath}> and name <{fileName}> success", UserName, filePath, fileName);
        return new PhysicalFileResult(filePath, attachment.ContentType ?? ContentType.Default) { FileDownloadName = attachment.Name ?? fileName };
    }
    /// <summary>
    /// 文件下载, 提供类似于文件夹目录结构的访问方式进行下载
    /// 文件路径只支持一级路径, 适用于访问自动保存的文件或者符合目录规范的文件
    /// 形如 2020-01-01/xxx.jpg
    /// </summary>
    /// <typeparam name="TAttachment">附件</typeparam>
    /// <param name="fileName">文件名</param>
    /// <param name="filePath">文件路径(只支持一级路径)</param>
    /// <returns></returns>
    public virtual async Task<TAttachment?> GetAttachmentAsync<TAttachment>(string? fileName, string? filePath = null) where TAttachment : class, IAttachment<TKey>, new()
    {
        fileName ??= _request.GetRequestOption<string>(nameof(fileName));
        filePath ??= _request.GetRequestOption<string>(nameof(filePath));
        if (string.IsNullOrWhiteSpace(fileName))
        {
            _logger.LogError("缺少必须的文件名!");
            throw new ArgumentNullException(fileName, "缺少必须的文件名!");
        }

        _logger.LogInformation("User {user} get the file from path <{filePath}> and name <{fileName}>", UserName, filePath, fileName);
        filePath = string.IsNullOrWhiteSpace(filePath) ? fileName : Path.Combine(filePath, fileName);
        return await GetQueryable<TAttachment>().FirstOrDefaultAsync(x => x.Path == filePath).ConfigureAwait(false);
    }
    /// <summary>
    /// 获取文件, 按业务类型和业务主键获取
    /// </summary>
    /// <typeparam name="TAttachment">附件</typeparam>
    /// <param name="businessType">业务类型</param>
    /// <param name="businessKey">业务主键</param>
    /// <returns></returns>
    public virtual async Task<IEnumerable<TAttachment>?> GetAttachmentsAsync<TAttachment>(string? businessType = null, TKey? businessKey = default) where TAttachment : class, IAttachment<TKey>, new()
    {
        businessType ??= _request.GetRequestOption<string>(nameof(businessType));
        businessKey ??= _request.GetRequestOption<TKey>(nameof(businessKey));
        _logger.LogInformation("User {user} query the attachment list with businessType {businessType} and businessKey {businessKey}", UserName, businessType, businessKey);
        if (string.IsNullOrWhiteSpace(businessType) || businessKey == null || businessKey.Equals(default))
        {
            throw new BadHttpRequestException("必须提供业务相关参数!");
        }

        var queryable = GetQueryable<TAttachment>();
        if (!string.IsNullOrWhiteSpace(businessType))
        {
            queryable = queryable.Where(x => x.BusinessType == businessType);
        }

        if (!(businessKey == null || businessKey.Equals(default)))
        {
            queryable = queryable.Where(x => x.BusinessKey != null && x.BusinessKey.Equals(businessKey));
        }

        var attachments = await queryable.ToListAsync();
        _logger.LogInformation("User {user} query the attachment list with businessType {businessType} and businessKey {businessKey} success", UserName, businessType, businessKey);
        return attachments;
    }

    /// <summary>
    /// 新增附件
    /// </summary>
    /// <typeparam name="TAttachment">附件</typeparam>
    /// <param name="attachments">附件集合</param>
    /// <param name="businessKey">业务主键</param>
    /// <param name="businessType">业务类型</param>
    /// <returns>待删除文件列表, 请在数据库事物提交成功后删除列表中的文件</returns>
    public virtual async Task AddAttachmentsAsync<TAttachment>(IEnumerable<TAttachment> attachments, TKey businessKey, string businessType) where TAttachment : class, IAttachment<TKey>, new()
    {
        if (attachments != null && attachments.Any())
        {
            var _dbSetAttachment = _context.Set<TAttachment>();
            foreach (var attachment in attachments)
            {
                attachment.BusinessKey ??= businessKey;
                attachment.BusinessType ??= businessType;
                Create(attachment);
                Detach(attachment);
                await _dbSetAttachment.AddAsync(attachment);
            }
        }
    }
    /// <summary>
    /// 修改附件, 保留旧附件, 新增新附件, 返回待删除文件列表
    /// </summary>
    /// <typeparam name="TAttachment">附件</typeparam>
    /// <param name="attachments">附件集合</param>
    /// <param name="businessKey">业务主键</param>
    /// <param name="businessType">业务类型</param>
    /// <param name="isLogical">是否逻辑操作, 默认不是</param>
    /// <returns>待删除文件列表, 请在数据库事物提交成功后删除列表中的文件</returns>
    public virtual async Task<IEnumerable<string>> UpdateAttachmentsAsync<TAttachment>(IEnumerable<TAttachment>? attachments, TKey businessKey, string businessType, bool isLogical = false) where TAttachment : class, IAttachment<TKey>, new()
    {
        var _dbSetAttachment = _context.Set<TAttachment>();
        var news = attachments ?? new List<TAttachment>();
        var olds = await GetQueryable<TAttachment>().Where(x => x.BusinessType == businessType && x.BusinessKey != null && x.BusinessKey.Equals(businessKey)).ToListAsync() ?? new List<TAttachment>();
        foreach (var attachment in news)
        {
            if (!olds.Any(x => x.Path == attachment.Path || x.Id.Equals(attachment.Id)))
            {
                //新的有旧的没有, 则新增, 附件只有新增和删除, 没有修改的说法
                attachment.BusinessKey ??= businessKey;
                attachment.BusinessType ??= businessType;
                Create(attachment);
                Detach(attachment);
                _dbSetAttachment.Add(attachment);
            }
        }

        var files = new List<string>();
        foreach (var attachment in olds)
        {
            //旧的有新的没有则删除
            //找 Id 或者 Path 值相等的, 新的里面找不到则删除旧的
            if ((!string.IsNullOrWhiteSpace(attachment.Path)) && (!news.Any(x => x.Id.Equals(attachment.Id) || x.Path == attachment.Path)))
            {
                files.Add(attachment.Path);
                Delete(attachment, isLogical);
            }
        }

        return files;
    }
    /// <summary>
    /// 自动删除附件, 返回待删除文件列表, 适用于实体对象模型从数据库删除时查找原始附件并自动删除
    /// </summary>
    /// <typeparam name="TAttachment">附件</typeparam>
    /// <param name="businessKey">业务主键</param>
    /// <param name="businessType">业务类型</param>
    /// <param name="isLogical">是否逻辑操作, 默认不是</param>
    /// <returns>待删除文件列表, 请在数据库事物提交成功后删除列表中的文件</returns>
    public virtual async Task<IEnumerable<string>?> DeleteAttachmentsAsync<TAttachment>(TKey businessKey, string? businessType = null, bool isLogical = false) where TAttachment : class, IAttachment<TKey>, new()
    {
        var queryable = GetQueryable<TAttachment>();
        if (businessKey != null && !businessKey.Equals(default))
        {
            queryable = queryable.Where(x => x.BusinessKey != null && x.BusinessKey.Equals(businessKey));
        }

        if (!string.IsNullOrWhiteSpace(businessType))
        {
            queryable = queryable.Where(x => x.BusinessType == businessType);
        }

        var attachments = await queryable.ToListAsync();
        if (attachments.Count < 1)
        {
            return null;
        }

        var files = attachments.Select(x => x.Path ?? string.Empty).Distinct();
        foreach (var attachment in attachments)
        {
            DeleteAsync(attachment, isLogical);
        }

        return files;
    }
    /// <summary>
    /// 按文件列表删除附件
    /// </summary>
    /// <typeparam name="TAttachment">附件</typeparam>
    /// <param name="fileNames">待删除的文件名</param>
    /// <param name="isLogical">是否逻辑操作, 默认不是</param>
    /// <returns></returns>
    public virtual async Task<IEnumerable<string>?> DeleteAttachmentsAsync<TAttachment>(IEnumerable<string> fileNames, bool isLogical = false) where TAttachment : class, IAttachment<TKey>, new()
    {
        var attachments = await GetQueryable<TAttachment>().Where(x => x.Path != null && fileNames.Contains(x.Path)).ToListAsync();
        if (attachments.Count < 1)
        {
            return null;
        }

        var files = attachments.Select(x => x.Path ?? string.Empty).Distinct();
        foreach (var attachment in attachments)
        {
            Delete(attachment, isLogical);
        }

        return files;
    }
    /// <summary>
    /// 单个文件上传
    /// </summary>
    /// <typeparam name="TAttachment">附件</typeparam>
    /// <param name="file"></param>
    /// <returns></returns>
    public virtual async Task<UploadResult<TAttachment, TKey>> UploadAsync<TAttachment>(IFormFile? file = null) where TAttachment : class, IAttachment<TKey>, new()
    {
        if (file == null && _request.HasFormContentType && _request.Form.Files.Any())
        {
            file = _request.Form.Files[0];
        }

        if (file == null)
        {
            throw new BadHttpRequestException("没有提交任何文件!");
        }

        var uploadResult = new UploadResult<TAttachment, TKey>
        {
            FileName = file.FileName,
            StatusCode = HttpStatusCode.NotAcceptable
        };

        if (file.Length <= 0)
        {
            uploadResult.Result = "文件内容为空!";
            return uploadResult;
        }

        if (file.Length > MaxUploadFileSize)
        {
            uploadResult.Result = $"文件 {file.Name} 大小超出 {MaxUploadFileSize} 单文件最大上传限制!";
            return uploadResult;
        }

        var ext = Path.GetExtension(file.FileName);
        var extUpper = ext.ToUpperInvariant();
        if (!AllowFileExtensions.Any(x => x == extUpper))
        {
            uploadResult.Result = "文件类型禁止上传!";
            return uploadResult;
        }

        var attachment = new TAttachment()
        {
            Name = file.FileName,
            Length = file.Length,
            Extension = ext,
            ContentType = file.ContentType
        };


        var attachmentPath = DateTime.UtcNow.ToString(AppSettings.DEFAULT_DATE_FORMAT, CultureInfo.InvariantCulture);
        var attachmentFile = GetAttachmentPath(attachmentPath);
        if (!Directory.Exists(attachmentFile))
        {
            Directory.CreateDirectory(attachmentFile);
        }

        var fileName = KeyGenerator.GetKey() + ext;
        attachment.Path = Path.Combine(attachmentPath, fileName);
        fileName = Path.Combine(attachmentFile, fileName);

        try
        {
            using (var stream = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Read))
            {
                await file.CopyToAsync(stream).ConfigureAwait(false);
                stream.Close();
            }

            uploadResult.StatusCode = HttpStatusCode.OK;
            uploadResult.Result = attachment.Path;
            uploadResult.Attachment = attachment;

            _logger.LogInformation("User {user} upload the file <{uploadFile}> success with result <{uploadResult}>!", UserName, uploadResult.FileName, uploadResult.Result);

            #region 图片文件创建缩略图
            if ((_appSetting.Attachment?.EnableImageThumbnail ?? false) && attachment.ContentType.ToUpperInvariant().StartsWith(nameof(Image).ToUpperInvariant(), StringComparison.InvariantCultureIgnoreCase))
            {
                await fileName.CreateThumbnailAsync(ImageThumbnailSize, ImageThumbnailSize, Path.Combine(attachmentFile, ImageThumbnailPrefix + Path.GetFileName(fileName)));
                _logger.LogInformation("User {user} upload the file <{uploadFile}> and create image thumbnail success with result <{uploadResult}>!", UserName, uploadResult.FileName, uploadResult.Result);
            }
            #endregion
        }
        catch (Exception ex)
        {
            uploadResult.StatusCode = HttpStatusCode.NotAcceptable;
            uploadResult.Result = ex.Message;
            _logger.LogError(ex, "User {user} upload the files <{uploadFile}> fail with result <{uploadResult}>!", UserName, uploadResult.FileName, ex.GetMessage());
        }

        return uploadResult;
    }
    /// <summary>
    /// 批量文件上传
    /// </summary>
    /// <typeparam name="TAttachment">附件</typeparam>
    /// <param name="files"></param>
    /// <returns></returns>
    public virtual async Task<IEnumerable<UploadResult<TAttachment, TKey>>> UploadAsync<TAttachment>(IEnumerable<IFormFile>? files = null) where TAttachment : class, IAttachment<TKey>, new()
    {
        if ((files == null || (!files.Any())) && _request.HasFormContentType && _request.Form.Files.IsNotNullOrEmpty())
        {
            files = _request.Form.Files;
        }

        if (files == null || (!files.Any()) || files.Sum(x => x.Length) <= 0)
        {
            throw new BadHttpRequestException("没有提交任何文件!");
        }

        if (files.Sum(x => x.Length) > TotalUploadFileSize)
        {
            throw new BadHttpRequestException($"文件总大小超出 {TotalUploadFileSize} 最大上传限制!");
        }

        var filePath = DateTime.UtcNow.ToString(AppSettings.DEFAULT_DATE_FORMAT, CultureInfo.InvariantCulture);
        var attachmentPath = GetAttachmentPath(filePath);
        if (!Directory.Exists(attachmentPath))
        {
            Directory.CreateDirectory(attachmentPath);
        }

        var uploadResults = new List<UploadResult<TAttachment, TKey>>();
        var allowFileExtensions = (_appSetting.Attachment?.AllowFileExtensions ?? AppSettings.DEFAULT_ALLOW_FILE_EXTENSIONS).Split(AppSettings.CHAR_COMMA, StringSplitOptions.RemoveEmptyEntries);
        foreach (var file in files)
        {
            uploadResults.Add(await UploadAsync<TAttachment>(file));
        }

        if (uploadResults.Count > 0)
        {
            //日志记录本次上传最终结果: 任意一个文件上传成功算成功
            var fileNames = uploadResults.Select(x => x.FileName ?? string.Empty).ToString<string>();
            var results = uploadResults.Select(x => x.Result ?? string.Empty).ToString<string>();
            if (uploadResults.Any(x => x.StatusCode == HttpStatusCode.OK))
            {
                _logger.LogInformation("User {user} upload the files <{uploadFiles}> success with result <{uploadResult}>!", UserName, fileNames, results);
            }
            else
            {
                _logger.LogWarning("User {user} upload the files <{uploadFiles}> fail with result <{uploadResult}>!", UserName, fileNames, results);
            }
        }

        return uploadResults;
    }
    #endregion
    #endregion

    #region 内部成员
    /// <summary>
    /// 获取是否逻辑操作的字符串
    /// </summary>
    /// <param name="isLogical"></param>
    /// <returns></returns>
    protected virtual string IsLogicalString(bool isLogical = false) => isLogical ? "logical" : "physical";
    /// <summary>
    /// 获取在更新复制属性值时需要忽略的字段配置
    /// </summary>
    /// <returns></returns>
    protected virtual TypeAdapterConfig GetAdapterConfig()
    {
        var config = new TypeAdapterConfig();
        var typeConfig = config.ForType<TEntitySet, TEntitySet>()
            //.PreserveReference(true)
            //.ShallowCopyForSameType(true)
            .AvoidInlineMapping(true)
            .MaxDepth(1)
            .Ignore(nameof(IEntitySet<TKey>.Id));
        if (_type.IsFromType<IEntitySetWithCreate<TKey>>())
        {
            typeConfig.Ignore(nameof(IEntitySetWithCreate<TKey>.RowVersion), nameof(IEntitySetWithCreate<TKey>.CreatedBy), nameof(IEntitySetWithCreate<TKey>.CreatedOn));
        }

        return config;
    }
    /// <summary>
    /// 获取第一个 BusinessType 特性标记的属性的 BusinessType Name
    /// 返回 BusinessType 的值
    /// </summary>
    /// <returns></returns>
    protected virtual string GetBusinessType()
    {
        var businessType = _type.Name;
        var propertyInfo = _type.GetProperties(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(x => x.HasAttribute<BusinessTypeAttribute>());
        if (propertyInfo != null)
        {
            businessType = propertyInfo.GetAttributeValue<BusinessTypeAttribute, string>(nameof(BusinessTypeAttribute.Name));
            businessType ??= _type.Name + AppSettings.CHAR_DOT + propertyInfo.Name;
        }

        return businessType;
    }
    /// <summary>
    /// 获取全部 BusinessType 特性标记的属性的 BusinessType Name 集合
    /// 返回属性 和 BusinessType 的值的集合
    /// </summary>
    /// <returns></returns>
    protected virtual IEnumerable<KeyValuePair<string, string>> GetBusinessTypes()
    {
        var dic = new Dictionary<string, string>();
        var propertyInfos = _type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.HasAttribute<BusinessTypeAttribute>());
        if (propertyInfos != null && propertyInfos.Any())
        {
            foreach (var propertyInfo in propertyInfos)
            {
                var businessType = propertyInfo.GetAttributeValue<BusinessTypeAttribute, string>(nameof(BusinessTypeAttribute.Name));
                businessType ??= _type.Name + AppSettings.CHAR_DOT + propertyInfo.Name;
                dic.Add(propertyInfo.Name, businessType);
            }
        }

        return dic;
    }
    /// <summary>
    /// 获取可用外键名或者抛出异常
    /// </summary>
    /// <typeparam name="TElement">集合元素数据类型</typeparam>
    /// <param name="foreignKey"></param>
    /// <returns></returns>
    /// <exception cref="ArgumentNullException"></exception>
    protected virtual string GetForeignKey<TElement>(string? foreignKey = null)
    {
        foreignKey ??= typeof(TElement).GetForeignKey<TEntitySet, TKey>();
        if (string.IsNullOrWhiteSpace(foreignKey))
        {
            throw new ArgumentNullException(nameof(foreignKey), "foreign key can't be null!");
        }

        return foreignKey;
    }
    /// <summary>
    /// 逻辑修改的独立操作, 需要当前对象实现自 IEntitySetWithCreate 接口
    /// </summary>
    /// <param name="entitySet">业务数据</param>
    protected virtual void LogicUpdate(TEntitySet entitySet)
    {
        if (entitySet is IEntitySetWithCreate<TKey> entitySetWithCreate)
        {
            //逻辑修改会产生一个当前数据不同主键的副本, 并设置为已更新的状态, 使用 RowVersion 字段关联更新后的数据编号
            var logicalCopy = entitySetWithCreate.Copy();
            logicalCopy.Id = KeyGenerator.GetKey<TKey>();
            logicalCopy.RowVersion = entitySetWithCreate.Id;
            logicalCopy.State = DataState.Updated;
            _dbSet.Add((TEntitySet)logicalCopy);
        }
    }
    #endregion
}

/// <summary>
/// 公共数据处理服务
/// 字符串作为键的默认实现
/// </summary>
/// <typeparam name="TDbContext">数据库上下文</typeparam>
/// <typeparam name="TEntitySet">业务数据类型</typeparam>
public class DataServiceX<TDbContext, TEntitySet> :
    DataServiceX<TDbContext, TEntitySet, string>,
    IDataService<TDbContext, TEntitySet>
    where TDbContext : DbContext
    where TEntitySet : class, IEntitySet<string>
{
    public DataServiceX(
        TDbContext context,
        ILogger<DataService<TDbContext, TEntitySet>> logger,
        IDistributedCache cache,
        IHttpContextAccessor httpContextAccessor,
        IMessageCommunicator communicator,
        AppSetting appSetting
        ) : base(context, logger, cache, httpContextAccessor, communicator, appSetting) { }
}